package cn.foolishbird.crow.spring.security.provider;

import cn.foolishbird.crow.spring.security.token.JWTAuthenticationToken;
import cn.foolishbird.crow.spring.security.token.JwtTokenAnalyzeStrategy;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.util.Assert;

import java.util.Objects;

/**
 * @author foolish bird
 **/
public class JWTAuthenticationProvider<P, T> extends AbstractAuthenticationProvider {
    // ~ Static fields/initializers
    // =====================================================================================

    /**
     * 获取用户信息
     */
    private final UserDetailsService userDetailsService;

    /**
     * jwtToken解析策略
     */
    private final JwtTokenAnalyzeStrategy<P, T> jwtTokenAnalyzeStrategy;


    public JWTAuthenticationProvider(UserDetailsService userDetailsService, JwtTokenAnalyzeStrategy jwtTokenAnalyzeStrategy) {
        this.userDetailsService = userDetailsService;
        this.jwtTokenAnalyzeStrategy = jwtTokenAnalyzeStrategy;
    }

    // ~ Methods
    // ========================================================================================================

    @Override
    public boolean supports(Class<?> authentication) {
        return JWTAuthenticationToken.class.isAssignableFrom(authentication);
    }

    @Override
    protected final UserDetails retrieveUser(String principal, Authentication authentication)
            throws AuthenticationException {
        try {
            UserDetails loadedUser = this.userDetailsService.loadUserByUsername(principal);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        } catch (UsernameNotFoundException | InternalAuthenticationServiceException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, Authentication authentication)
            throws AuthenticationException {

        if (Objects.isNull(authentication.getCredentials())) {
            logger.debug("Authentication failed: no credentials provided");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }

        if (!jwtTokenAnalyzeStrategy.verifierJwtToken(authentication.getCredentials().toString())) {
            logger.debug("Authentication failed: no credentials provided");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }
    }

    @Override
    protected void doAfterPropertiesSet() {
        Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
    }

}
